package rabid;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import common.Constants;

public class Cluster {
  private final int NUM_OF_SETS = 7;
  private Vector clusterSets;
  private Map centroidSetMap;
  
  //1. Divide the List of index files into 7 sets.
  //2. Compute the centroid of each set.
  //3. Iterate over each file in List and move it to the centroid set it is closest too.
  //4. Repeat until no files are moved between sets. 
  public Cluster(List indexFiles){
    System.out.print("Clustering ... ");
    
    if(indexFiles.size() <= Constants.MAX_NUM_IN_SET) {
      clusterSets = getClusterSets(indexFiles);      
    }
    else {
      List listOfSets = initSetList(indexFiles);    
      converge(listOfSets);
    }
    System.out.println("done.");
  }
  
  private List initSetList(List indexFiles){
    ArrayList listOfSets = new ArrayList();
    for(int i = 0; i < NUM_OF_SETS; i++){
      listOfSets.add(new ArrayList());
    }
    
    Iterator itr = indexFiles.iterator();
    for(int i = 0; i < NUM_OF_SETS && itr.hasNext(); i++) {
      List set = (List)listOfSets.get(i);
      set.add(itr.next());
      
      if(i+1 == NUM_OF_SETS) i = -1; //i will be incremented to 0
    }
    
    return listOfSets;
  }
  
  private void converge(List listOfSets){
    Map centroidSetMap = getCentroids(listOfSets);    
    Map newCentroidSetMap = createBlankCentroidSetMap(centroidSetMap);    
    boolean pointsMoved = clusterSets(centroidSetMap, newCentroidSetMap);
    
    if(pointsMoved) {
      List newListOfSets = new ArrayList();
      Iterator newCentroids = newCentroidSetMap.keySet().iterator();
      while(newCentroids.hasNext()) {
        newListOfSets.add(newCentroidSetMap.get(newCentroids.next()));
      }
      converge(newListOfSets);
    }
    else {
      this.centroidSetMap = newCentroidSetMap;
      clusterSets = getClusterSets();
    }
  }
  
  private Map getCentroids(List listOfSets) {
    HashMap centroidSetMap = new HashMap();
    Iterator sets = listOfSets.iterator();
    while(sets.hasNext()) {
      List listOfFiles = (List)sets.next();
      centroidSetMap.put(new Centroid(listOfFiles), listOfFiles);
    }
    return centroidSetMap;
  }
  
  private Map createBlankCentroidSetMap(Map centroidSetMap) {
    HashMap newCentroidSetMap = new HashMap();
    Iterator centroids = centroidSetMap.keySet().iterator();
    while(centroids.hasNext()) {
      newCentroidSetMap.put(centroids.next(), new ArrayList());
    }
    return newCentroidSetMap;
  }
  
  private boolean clusterSets(Map centroidSetMap, Map newCentroidSetMap) {
    boolean pointsMoved = false;
    Iterator centroids = centroidSetMap.keySet().iterator();
    while(centroids.hasNext()) {
      Centroid currCentroid = (Centroid)centroids.next();
      List listOfFiles = (List)centroidSetMap.get(currCentroid);
      Iterator files = listOfFiles.iterator();
      while(files.hasNext()) {
        IndexedFile indexedFile = (IndexedFile)files.next();
        Centroid closestCentroid = getClosestCentroid(currCentroid, indexedFile, newCentroidSetMap);
        pointsMoved = !closestCentroid.equals(currCentroid);
                
        List newListOfFiles = (List)newCentroidSetMap.get(closestCentroid);
        newListOfFiles.add(indexedFile);
        newCentroidSetMap.put(closestCentroid, newListOfFiles);
      }
    }
    return pointsMoved;
  }
  
  private Centroid getClosestCentroid(Centroid currCentroid, IndexedFile indexedFile, Map newCentroidSetMap) {
    Centroid closestCentroid = currCentroid;
    int currCloseness = currCentroid.computeCloseness(indexedFile);
    
    //find the closest centroid
    Iterator newCentroids = newCentroidSetMap.keySet().iterator();
    while(newCentroids.hasNext()) {
      Centroid newCentroid = (Centroid)newCentroids.next();
      int newCloseness = newCentroid.computeCloseness(indexedFile);
      if(newCloseness < currCloseness) {
        closestCentroid = newCentroid;
      }
    }
    return closestCentroid;
  }
  
  private Vector getClusterSets(List indexFiles) {
    Vector sets = new Vector();
    Iterator files = indexFiles.iterator();
    while(files.hasNext()) {
      IndexedFile indexedFile = (IndexedFile)files.next();
      sets.add(new ClusterSet(indexFiles, indexedFile.getFileName()));
    }
    return sets;    
  }

  private Vector getClusterSets() {
    Vector sets = new Vector();
    Iterator centroids = centroidSetMap.keySet().iterator();
    while(centroids.hasNext()) {
      Centroid centroid = (Centroid)centroids.next();
      List listOfFiles = (List)centroidSetMap.get(centroid);
      sets.add(new ClusterSet(listOfFiles, centroid.getMostFrequentTerm()));
    }
    return sets;
  }
  
  public Vector getSets() {
    return clusterSets;
  }
}
